capsule AI-native Unix-like composition layer

src/api/img/[name].js

1,834 bytes · 48 lines · capsule://quake0day/[email protected] raw on github

// GET /api/img/<key> → serves image from R2
export async function onRequest(context) {
  const { params, env, request } = context;
  if (!env.YL_IMAGES) return new Response("R2 not bound", { status: 500 });

  const key = decodeURIComponent(params.name || "");
  if (!key || key.includes("/") || key.includes("..")) {
    return new Response("Bad key", { status: 400 });
  }

  // Use Workers Cache API
  const cache = caches.default;
  const cacheUrl = new URL(request.url);
  const cacheKey = new Request(cacheUrl.toString(), { method: "GET" });
  const cached = await cache.match(cacheKey);
  if (cached) return cached;

  const obj = await env.YL_IMAGES.get(key);
  if (!obj) return new Response("Not found", { status: 404 });

  const headers = new Headers();
  obj.writeHttpMetadata(headers);
  headers.set("Cache-Control", "public, max-age=31536000, immutable");
  headers.set("ETag", obj.httpEtag);

  // ETag conditional
  const ifNoneMatch = request.headers.get("If-None-Match");
  if (ifNoneMatch && ifNoneMatch === obj.httpEtag) {
    return new Response(null, { status: 304, headers });
  }

  const res = new Response(obj.body, { headers });
  context.waitUntil(cache.put(cacheKey, res.clone()));
  return res;
}

// DELETE /api/img/<key> → delete (auth required)
export async function onRequestDelete(context) {
  const { params, env, request } = context;
  const { isAuthed, json, unauthorized } = await import("../../_lib/auth.js");
  if (!await isAuthed(request, env)) return unauthorized();
  if (!env.YL_IMAGES) return json({ error: "R2 not bound" }, { status: 500 });
  const key = decodeURIComponent(params.name || "");
  if (!key || key.includes("/") || key.includes("..")) return json({ error: "Bad key" }, { status: 400 });
  await env.YL_IMAGES.delete(key);
  return json({ ok: true, deleted: key });
}